package com.ns.school.common.utils;

import com.alibaba.fastjson2.JSONObject;
import com.ns.school.common.exception.MyException;
import jakarta.servlet.ServletRequest;
import org.apache.commons.io.FilenameUtils;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.StandardCookieSpec;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder;
import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
import org.apache.hc.core5.pool.PoolReusePolicy;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class HttpUtil {

    private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);

    public static final String CONTENT_TYPE = "Content-Type";
    public static final String APP_JSON_UTF8 = "application/json;charset=UTF-8";
    public static final String APP_FORM_UTF8 = "application/x-www-form-urlencoded;charset=UTF-8";

    public static final String USER_AGENT = "User-Agent";
    public static final String USER_AGENT_CONTENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36";

    public static final String CHARSET = "charset";
    public static final String UTF8 = "UTF-8";
    public static final int TIME_OUT = 20;
    public static final Timeout TIME_OUT_SEC = Timeout.ofSeconds(TIME_OUT);
    public static final CloseableHttpClient HTTP_CLIENT;
    public static final PoolingHttpClientConnectionManager CONN_MGR;

    private static final RequestConfig REQUEST_CONFIG = RequestConfig.custom().setResponseTimeout(TIME_OUT_SEC).setConnectTimeout(TIME_OUT_SEC).setConnectionRequestTimeout(TIME_OUT_SEC).build();

    static {
        CONN_MGR = PoolingHttpClientConnectionManagerBuilder.create()
                .setMaxConnTotal(1200) // 连接池最大连接数
                .setMaxConnPerRoute(500) // 单域名最大的连接数
                .setTlsSocketStrategy((TlsSocketStrategy) ClientTlsStrategyBuilder.create()
                        .setSslContext(SSLContexts.createSystemDefault())
                        .setTlsVersions(TLS.V_1_0, TLS.V_1_1, TLS.V_1_2, TLS.V_1_3)
                        .buildAsync())
                .setDefaultSocketConfig(SocketConfig.custom()
                        .setSoTimeout(Timeout.ofSeconds(TIME_OUT * 2))
                        .build())
                .setPoolConcurrencyPolicy(PoolConcurrencyPolicy.STRICT)
                .setConnPoolPolicy(PoolReusePolicy.LIFO)
                .setDefaultConnectionConfig(ConnectionConfig.custom()
                        .setSocketTimeout(TIME_OUT_SEC)
                        .setConnectTimeout(TIME_OUT_SEC)
                        .setTimeToLive(TimeValue.ofMinutes(TIME_OUT))
                        .build())
                .build();

        HTTP_CLIENT = HttpClients.custom()
                .setConnectionManager(CONN_MGR)
                .setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(StandardCookieSpec.STRICT).build())
                .build();
    }

    /**
     * get方式请求
     *
     * @param httpUrl
     * @return
     */
    public static String get(String httpUrl) {
        return get(httpUrl, null, null);
    }

    /**
     * @param httpUrl
     * @param params
     * @return
     */
    public static String get(String httpUrl, Map<String, Object> headers, Map<String, Object> params) {
        HttpGet request = new HttpGet(httpUrl);
        request.setConfig(REQUEST_CONFIG);

        // 设置header
        setHeader(request, headers);
        // 设置参数
        httpUrl = getPathWithParams(httpUrl, params);

        try {
            return HTTP_CLIENT.execute(request, HttpUtil::getResponse);
        } catch (Exception e) {
            log.error("HttpUtil >> e:{}; URL:{}", e, httpUrl);
            throw new MyException(e);
        }
    }

    /**
     * getStream
     *
     * @param httpUrl
     * @return
     */
    public static InputStream getStream(String httpUrl) {
        try {
            HttpGet request = new HttpGet(httpUrl);
            request.setConfig(REQUEST_CONFIG);
            return getStreamContent(request, null, null);
        } catch (Exception e) {
            log.error("HttpUtil >> e:{}; URL:{}", e, httpUrl);
            throw new MyException(e);
        }
    }

    /**
     * post form请求
     *
     * @param httpUrl
     * @return
     */
    public static String post(String httpUrl) {
        return post(httpUrl, null, null);
    }

    /**
     * post form请求
     *
     * @param httpUrl
     * @param params
     * @return
     */
    public static String post(String httpUrl, Map<String, Object> params) {
        return post(httpUrl, null, params);
    }

    /**
     * post form请求
     *
     * @param httpUrl
     * @param params
     * @return
     */
    public static String post(String httpUrl, Map<String, String> headers, Map<String, Object> params) {
        HttpPost request = new HttpPost(httpUrl);
        request.setConfig(REQUEST_CONFIG);
        request.setHeader(CONTENT_TYPE, APP_FORM_UTF8);
        request.setHeader(USER_AGENT, USER_AGENT_CONTENT);
        // 设置header
        setHeader(request, headers);
        // 设置参数
        setParams(request, params);
        try {
            return HTTP_CLIENT.execute(request, HttpUtil::getResponse);
        } catch (Exception e) {
            log.error("HttpUtil >> e:{}; URL:{}", e, httpUrl);
            throw new MyException(e);
        }
    }

    /**
     * post json请求
     *
     * @param httpUrl
     * @return
     */
    public static String postJson(String httpUrl) {
        HttpPost request = new HttpPost(httpUrl);
        request.setConfig(REQUEST_CONFIG);
        request.setHeader(CONTENT_TYPE, APP_JSON_UTF8);
        try {
            return HTTP_CLIENT.execute(request, HttpUtil::getResponse);
        } catch (Exception e) {
            log.error("HttpUtil >> e:{}; URL:{}", e, httpUrl);
            throw new MyException(e);
        }
    }

    /**
     * post json请求
     *
     * @param httpUrl
     * @param json
     * @return
     */
    public static String postJson(String httpUrl, String json) {
        HttpPost request = new HttpPost(httpUrl);
        request.setConfig(REQUEST_CONFIG);
        request.setHeader(CONTENT_TYPE, APP_JSON_UTF8);
        if (StrUtil.notEmpty(json)) {
            request.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON, UTF8, false));
        }
        try {
            return HTTP_CLIENT.execute(request, HttpUtil::getResponse);
        } catch (Exception e) {
            log.error("HttpUtil >> e:{}; URL:{}", e, httpUrl);
            throw new MyException(e);
        }
    }

    /**
     * post json请求
     *
     * @param httpUrl
     * @param json
     * @return
     */
    public static String postJson(String httpUrl, JSONObject json) {
        HttpPost request = new HttpPost(httpUrl);
        request.setConfig(REQUEST_CONFIG);
        request.setHeader(CONTENT_TYPE, APP_JSON_UTF8);
        if (StrUtil.notEmpty(json)) {
            request.setEntity(new StringEntity(json.toString(), ContentType.APPLICATION_JSON, UTF8, false));
        }
        try {
            return HTTP_CLIENT.execute(request, HttpUtil::getResponse);
        } catch (Exception e) {
            log.error("HttpUtil >> e:{}; URL:{}", e, httpUrl);
            throw new MyException(e);
        }
    }

    /**
     * post json请求
     *
     * @param httpUrl
     * @param json
     * @param headers
     * @return
     */
    public static String postJson(String httpUrl, JSONObject json, Map<String, String> headers) {
        HttpPost request = new HttpPost(httpUrl);
        request.setConfig(REQUEST_CONFIG);
        request.setHeader(CONTENT_TYPE, APP_JSON_UTF8);
        // 设置header
        setHeader(request, headers);
        if (StrUtil.notEmpty(json)) {
            request.setEntity(new StringEntity(json.toString(), ContentType.APPLICATION_JSON, UTF8, false));
        }
        try {
            return HTTP_CLIENT.execute(request, HttpUtil::getResponse);
        } catch (Exception e) {
            log.error("HttpUtil >> e:{}; URL:{}", e, httpUrl);
            throw new MyException(e);
        }
    }

    /**
     * getStream
     *
     * @param httpUrl
     * @param json
     * @return
     */
    public static InputStream postStream(String httpUrl, String json) {
        try {
            HttpPost request = new HttpPost(httpUrl);
            request.setConfig(REQUEST_CONFIG);
            request.setHeader(CONTENT_TYPE, APP_JSON_UTF8);
            request.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
            return getStreamContent(request, null, null);
        } catch (Exception e) {
            log.error("HttpUtil >> e:{}; URL:{}", e, httpUrl);
            throw new MyException(e);
        }
    }

    /**
     * post form请求,返回InputStream
     *
     * @param httpUrl
     * @param params
     * @return
     */
    public static InputStream postStream(String httpUrl, Map<String, Object> headers, Map<String, Object> params) {
        HttpPost request = new HttpPost(httpUrl);
        request.setConfig(REQUEST_CONFIG);
        request.setHeader(CONTENT_TYPE, APP_FORM_UTF8);
        request.setHeader(USER_AGENT, USER_AGENT_CONTENT);

        // 设置header
        setHeader(request, headers);
        // 设置参数
        setParams(request, params);

        return getStreamContent(request, headers, UTF8);
    }

    private static List<NameValuePair> buildPairs(Map<String, ?> params) {
        List<NameValuePair> pairs = new ArrayList<>();
        if (params != null && !params.isEmpty()) {
            Set<String> keys = params.keySet();
            for (String key : keys) {
                Object v = params.get(key);
                if (v != null) {
                    pairs.add(new BasicNameValuePair(key, v.toString()));
                }
            }
        }
        return pairs;
    }

    private static void setHeader(HttpUriRequestBase request, Map<String, ?> headers) {
        if (headers != null && !headers.isEmpty()) {
            for (String key : headers.keySet()) {
                request.addHeader(key, StrUtil.toString(headers.get(key)));
            }
        }
    }

    private static void setHeader(HttpUriRequestBase request, Map<String, ?> headers, String charset) {
        request.setHeader(USER_AGENT, USER_AGENT_CONTENT);
        if (StrUtil.isEmpty(charset)) {
            request.setHeader(CHARSET, UTF8);
        } else {
            request.setHeader(CHARSET, charset);
        }
        if (headers != null && !headers.isEmpty()) {
            Set<String> keys = headers.keySet();
            for (String key : keys) {
                Object o = headers.get(key);
                if (o != null) {
                    request.setHeader(key, o.toString());
                }
            }
        }

        Header[] hs = request.getHeaders(CONTENT_TYPE);
        if (hs == null || hs.length == 0) {
            request.setHeader(CONTENT_TYPE, APP_FORM_UTF8);
        }
    }

    private static void setParams(HttpUriRequestBase request, Map<String, ?> params) {
        List<NameValuePair> pairs = buildPairs(params);
        if (!pairs.isEmpty()) {
            request.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8));
        }
    }

    private static String getResponse(ClassicHttpResponse response) throws IOException {
        HttpEntity responseEntity = response.getEntity();
        try (response) {
            return EntityUtils.toString(responseEntity, UTF8);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // 确保流被完全消费
            EntityUtils.consume(responseEntity);
        }
    }

    private static InputStream getStreamContent(HttpUriRequestBase request, Map<String, ?> headers, String charset) {
        setHeader(request, headers, charset);
        try {
            return HTTP_CLIENT.execute(request, response -> {
                HttpEntity responseEntity = response.getEntity();
                try (response) {
                    ByteArrayOutputStream stream = cloneInputStream(responseEntity.getContent());
                    if (stream != null) {
                        return new ByteArrayInputStream(stream.toByteArray());
                    }
                    return null;
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    // 确保流被完全消费
                    EntityUtils.consume(responseEntity);
                }
            });
        } catch (Exception e) {
            log.error("HttpUtil >> e:{}; URL:{}", e, request.getRequestUri());
            throw new MyException(e);
        }
    }

    private static ByteArrayOutputStream cloneInputStream(InputStream input) {
        try {
            if (input == null) {
                return null;
            }
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int len;
            while ((len = input.read(buffer)) > -1) {
                stream.write(buffer, 0, len);
            }
            stream.flush();
            return stream;
        } catch (IOException e) {
            log.error("cloneInputStream error:", e);
            throw new MyException(e);
        }
    }

    private static String getPathWithParams(String httpUrl, Map<String, ?> params) {
        StringBuilder sb = new StringBuilder(httpUrl);
        if (params != null && !params.isEmpty()) {
            if (httpUrl.contains("?")) {
                if (sb.charAt(sb.length() - 1) != '&') {
                    sb.append("&");
                }
            } else {
                sb.append("?");
            }
            Set<String> keys = params.keySet();
            for (String key : keys) {
                Object val = params.get(key);
                if (val != null && !"".equals(val)) {
                    sb.append(key).append("=").append(URLEncoder.encode(val.toString(), StandardCharsets.UTF_8)).append("&");
                }
            }
            if (sb.charAt(sb.length() - 1) == '&') {
                sb.deleteCharAt(sb.length() - 1);
            }
        }
        return sb.toString();
    }

    /**
     * 读取Body里面的请求数据(json/xml)
     *
     * @param request
     * @return
     */
    public static String getBodyString(ServletRequest request) {
        StringBuilder body = new StringBuilder();
        try (BufferedReader reader = request.getReader()) {
            String str;
            while ((str = reader.readLine()) != null) {
                body.append(str);
            }
            return body.toString();
        } catch (IOException e) {
            log.error("请求参数转换异常！ params=[{}]", body);
            throw new MyException("转换异常");
        }
    }

    /**
     * 上传文件
     *
     * @param httpUrl 请求地址
     * @param fileKey media
     * @param file    file
     * @return
     */
    public static String fileUpload(String httpUrl, String fileKey, File file) {
        HashMap<String, Object> map = new HashMap<>(2);// 1*0.75+1=1.75
        map.put(fileKey, file);
        return fileUpload(httpUrl, map);
    }

    /**
     * 上传文件
     *
     * @param httpUrl 请求地址
     * @param params  {"media", file}
     * @return
     */
    public static String fileUpload(String httpUrl, Map<String, Object> params) {
        HttpPost request = new HttpPost(httpUrl);
        request.setHeader(CHARSET, UTF8);
        // HttpMultipartMode.RFC6532参数的设定是为避免文件名为中文时乱码
        MultipartEntityBuilder builder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.EXTENDED);
        for (String param : params.keySet()) {
            Object value = params.get(param);
            if (value == null) {
                continue;
            }
            if (value instanceof File) {
                builder.addBinaryBody(param, (File) value);
            } else {
                builder.addTextBody(param, StrUtil.toString(value), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8));
            }
        }
        HttpEntity entity = builder.build();
        request.setEntity(entity);

        // 执行提交
        try {
            return HTTP_CLIENT.execute(request, response -> {
                StringBuilder result = new StringBuilder();
                // 接收调用外部接口返回的内容
                HttpEntity responseEntity = response.getEntity();
                if (response.getCode() == HttpStatus.SC_OK) {
                    // 返回的内容都在content中
                    // 定义BufferedReader输入流来读取URL的响应
                    try (InputStream content = responseEntity.getContent();
                         InputStreamReader iReader = new InputStreamReader(content);
                         BufferedReader in = new BufferedReader(iReader)) {
                        String line;
                        while ((line = in.readLine()) != null) {
                            result.append(line);
                        }
                    }
                }
                return result.toString();
            });
        } catch (Exception e) {
            log.error("上传文件失败：", e);
            throw new MyException("上传文件失败");
        }
    }

    /**
     * 获取网络文件
     *
     * @param httpUrl
     * @return
     */
    public static File getNetWorkFileLoop(String httpUrl) {
        if (StrUtil.isEmpty(httpUrl) || !httpUrl.startsWith("http")) {
            return null;
        }
        File file = null;
        int count = 0;

        do {
            count++;
            try {
                file = getNetWorkFile(httpUrl, FilenameUtils.getExtension(httpUrl));
                if (file != null) {
                    break;
                } else {
                    log.error("网络文件:{};读取失败,延迟2秒发起,第:{}次尝试读取", httpUrl, count);
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            } catch (Exception e) {
                log.error("获取网络文件失败:{}", httpUrl, e);
            }
        } while (count < 6);
        return file;
    }

    /**
     * 获取网络文件
     *
     * @param httpUrl
     * @return
     */
    public static File getNetWorkFile(String httpUrl, String extName) {
        if (StrUtil.isEmpty(httpUrl) || !httpUrl.startsWith("http")) {
            return null;
        }
        try (InputStream bis = HttpUtil.getStream(httpUrl.trim())) {
            if (bis == null) {
                log.error("网络文件:{};读取失败", httpUrl);
                return null;
            }

            File file = new File(StrUtil.getRandomFileName() + "." + extName);
            try (FileOutputStream fos = new FileOutputStream(file)) {
                int size = 0;
                byte[] buffer = new byte[2048];
                while ((size = bis.read(buffer)) != -1) {
                    fos.write(buffer, 0, size);
                }
                fos.flush();
                return file;
            }
        } catch (Exception e) {
            log.error("获取网络文件失败:{}", httpUrl, e);
            throw new RuntimeException("获取网络文件失败");
        }
    }
}
